﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Threading;
using System.ComponentModel;
using Common.Extensions;
using System.Threading.Tasks;

namespace Common.Model
{
    [DataContract]
    public class CKISpieler : CSpieler
    {
        public enum Level { JustRandom = 0, Easy = 1, EasyHard = 2, Middle = 3, MiddleHard = 4, Hard = 5}

        #region feldgewichtung
        #region feldgewichtung von gnothello (8*8)
        private static int[,] _FeldGewichtung8 = new int[8, 8]{
            {512,   4, 128, 256, 256, 128,   4, 512},
            {  4,   2,   8,  16,  16,   8,   2,   4},
            {128,   8,  64,  32,  32,  64,   8, 128},
            {256,  16,  32,   2,   2,  32,  16, 256},
            {256,  16,  32,   2,   2,  32,  16, 256},
            {128,   8,  64,  32,  32,  64,   8, 128},
            {  4,   2,   8,  16,  16,   8,   2,   4},
            {512,   4, 128, 256, 256, 128,   4, 512}
        };
        #endregion
        private static int[,] _FeldGewichtung;
        private static int[,] FeldGewichtung
        {
            get
            {
                #region generate
                if (_FeldGewichtung == null)
                {
                    int size = CSpielfeld.Instance.Size;

                    if (size == 8)
                    {
                        _FeldGewichtung = _FeldGewichtung8;
                        return _FeldGewichtung;
                    }

                    _FeldGewichtung = new int[size, size];

                    int gewichtRegionEcken = 512, gewichtRegionRand = 256, gewichtRegionNormal = 64, gewichtRegionRand2 = 16, gewichtRegionSchlecht = 2;

                    foreach (CPoint currentPoint in CSpielfeld.Instance.AllPoints)
                    {
                        #region schlechte punkte
                        if (((currentPoint.X == 1 || currentPoint.X == size - 2) &&
                                (currentPoint.Y == 2 || currentPoint.Y == size - 2)) ||
                           ((currentPoint.Y == 1 || currentPoint.Y == size - 2) &&
                                (currentPoint.Y == 2 || currentPoint.Y == size - 2)))
                        {
                            _FeldGewichtung[currentPoint.Y, currentPoint.X] = gewichtRegionSchlecht;
                            continue;
                        }
                        #endregion

                        #region eckpunkte
                        if ((currentPoint.X == 0 && (currentPoint.Y == 0 || currentPoint.Y == size - 1)) ||
                           (currentPoint.X == size - 1 && (currentPoint.Y == 0 || currentPoint.Y == size - 1)))
                        {
                            _FeldGewichtung[currentPoint.Y, currentPoint.X] = gewichtRegionEcken;
                            continue;
                        }
                        #endregion

                        #region randpunkte
                        if (((currentPoint.X == 0 || currentPoint.X == size - 1)
                                && (currentPoint.Y > 1 && currentPoint.Y < size - 2)) ||
                            ((currentPoint.Y == 0 || currentPoint.Y == size - 1)
                                && (currentPoint.X > 1 && currentPoint.X < size - 2)))
                        {
                            _FeldGewichtung[currentPoint.Y, currentPoint.X] = gewichtRegionRand;
                            continue;
                        }
                        #endregion

                        #region randpunkte2
                        if (((currentPoint.X == 1 || currentPoint.X == size - 2) &&
                                (currentPoint.Y > 1 && currentPoint.Y < size - 2)) ||
                           ((currentPoint.Y == 1 || currentPoint.Y == size - 2) &&
                                (currentPoint.X > 1 && currentPoint.X < size - 2))
                            )
                        {
                            _FeldGewichtung[currentPoint.Y, currentPoint.X] = gewichtRegionRand2;
                            continue;
                        }
                        #endregion

                        _FeldGewichtung[currentPoint.Y, currentPoint.X] = gewichtRegionNormal;
                    }
                }
                #endregion
                return _FeldGewichtung;
            }
        }
        #endregion

        private Action<CPoint> AsyncGetNextTurnCallback = null;

#if DEBUG
        Stopwatch stopW = new Stopwatch();
#endif

        #region getter / setter
        [DataMember]
        public Level level { get; set; }

        #endregion

        #region konstruktor
        public CKISpieler()
            : base()
        {
            this.level = Level.JustRandom;
        }

        public CKISpieler(CSpielfeld.FieldColor col, Level l = Level.JustRandom, String name = "")
            : base(col, name)
        {
            this.level = l;

        }
        #endregion

        /// <summary>
        /// setzt die feldgewichtung neu
        /// </summary>
        public static void reSetFeldgewichtung()
        {
            _FeldGewichtung = null;
        }

        /// <summary>
        /// giebt den nächsten zug entsprechend des levels zurück
        /// </summary>
        /// <returns></returns>
        public CPoint getNextTurn()
        {
            if (CSpielmanager.Instance.SpielFertig)
                throw new Exception("Ein KI-Spieler kann keinen Zug vornehmen, wenn das Spiel fertig ist oder noch nicht begonnen hat.");

            List<CPoint> possible = CSpielfeld.Instance.reCalculatePossibleFields(this);

            switch (level)
            {
                case Level.JustRandom: return possible.getRandom();
                default:
                    CPoint p = getBestMove((int)level);
                    if (p == null)
                        return possible.getRandom();
                    return p;
            }
        }

        /// <summary>
        /// berechnet den nächsten zug asynchron und giebt ihn im callback entsprechend des levels zurück
        /// </summary>
        /// <param name="callback"></param>
        public void getNextTurnAsync(Action<CPoint> callback)
        {
#if DEBUG
            stopW.Reset();
            stopW.Start();
#endif

            this.AsyncGetNextTurnCallback = callback;
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += (sender, e) => e.Result = getNextTurn();
            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
            bw.RunWorkerAsync();
        }
        #region backgroundworker
        private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (AsyncGetNextTurnCallback != null)
            {
                AsyncGetNextTurnCallback(e.Result as CPoint);
                AsyncGetNextTurnCallback = null;
            }

#if DEBUG
            stopW.Stop();
            Debug.Print((e.Result as CPoint).ToString(true) + " scheint der beste Zug zu sein.");
            Debug.Print("Zugberechnung hat in Level " + level.ToString() + "  " + TimeSpan.FromMilliseconds(stopW.ElapsedMilliseconds).ToString() + " gedauert.");
#endif
        }
        #endregion

        /// <summary>
        /// berechnet den besten zug für den aktuellen spieler
        /// </summary>
        /// <param name="countCalcNextMoves">giebt an, wieviele zukünftige Züge in die Berechnung einbezogen werden sollen</param>
        /// <returns></returns>
        private CPoint getBestMove(int countCalcNextMoves)
        {
            int maxWeight = 0, currentWeight;

            List<CPoint> resultPoint = new List<CPoint>();
            List<CPoint> possibleMoves = CSpielfeld.Instance.reCalculatePossibleFields(this);//alle möglichen spielzüge holen

            if (possibleMoves.Count <= 0) return null;
            if (possibleMoves.Count == 1) return possibleMoves.First();

#if DEBUG
            Debug.Print("Mögliche Züge:");
#endif

            /* berechne die stärke jedes zuges */
            possibleMoves.ForEach((possiblePoint) =>
            {
                currentWeight = getMovesWight(possiblePoint, this.Farbe, countCalcNextMoves);

#if DEBUG
                Debug.Print(possiblePoint.ToString(true) + ": " + currentWeight.ToString());
#endif

                if (currentWeight >= maxWeight || maxWeight == 0)//die gewichtung jedes zuges errechnen und entsprechend mit den anderen vergleichen
                {
                    if (currentWeight != maxWeight)
                        resultPoint.Clear();
                    resultPoint.Add(possiblePoint);
                    maxWeight = currentWeight;
                }
            });
            return resultPoint.getRandom();
        }

        /// <summary>
        /// berechnet ein gewicht eines zuges anhand der FeldGewichtung
        /// </summary>
        /// <param name="p">Punkt(Zug), für den die Gewichtung berechnet werden soll</param>
        /// <param name="col">Farbe, welche den aktuellen Zug ausführen soll</param>
        /// <param name="countCalcNextMoves">Anzahl zukünftiger Züge, welche miteinberechnet werden sollen</param>
        /// <param name="sf">Spielfeld, welches verwendet werden soll, um die Spielzüge zu simulieren, falls keines angegeben wird, wird eine Kopie des aktuellen Spielfelds erstellt</param>
        /// <returns></returns>
        private int getMovesWight(CPoint p, CSpielfeld.FieldColor col, int countCalcNextMoves)
        {
            CSpielfeld original = new CSpielfeld(CSpielfeld.Instance);

            /* gewichtung zug an sich, gewichtung der nechsten Züge */
            int weight = 0, nextMovesWeight = 0;

            /* berechne das spielfeld neu für den aktuellen spieler */
            CSpielfeld.Instance.reCalculatePossibleFields(col);

            /* liste von punkten holen, welche gedreht werden */
            List<CPoint> gedreht;
            CSpielfeld.Instance.turn(p, col, out gedreht);

            /* gewichtung jeder dieser Punkte addieren */
            gedreht.ForEach((pp) => weight += FeldGewichtung[pp.Y, pp.X]);

            /* 
             * falls ich am Spielzug bin, fällt die Gwichtung positiv aus, falls nicht ich am zur zeit berechnenden Spielzug bin,
             * so fällt die gewichtung negativ aus, dh. dann muss der schlechteste Spielzug gewählt werden 
             */
            if (col != this.Farbe) weight *= -1;

            col = col == CSpielfeld.FieldColor.Black ? CSpielfeld.FieldColor.White : CSpielfeld.FieldColor.Black;

            /*
             * Berechne nun die Zukünftigen Züge
             */
            if (countCalcNextMoves-- > 0)
            {
                CSpielfeld.Instance.reCalculatePossibleFields(col).ForEach((pp) =>
                {
                    /*
                     * der schlechteste fall(worstcase, dh. es wird angenommen der gegner führt den best möglichen zug aus)
                     * wird hinzugefügt
                     */
                    int tmp = getMovesWight(pp, col, countCalcNextMoves);
                    if (tmp < nextMovesWeight) nextMovesWeight = tmp;
                });
            }
            CSpielfeld.Instance = original;
            return weight + nextMovesWeight;
        }

        public override string ToString()
        {
            try
            {
                return "CKISpieler [ Level=" + this.level.ToString() +", CSpieler=" + base.ToString() + " ]";
            }
            catch (Exception)
            {
                return base.ToString();
            }
        }
        public override bool Equals(object obj)
        {
            CKISpieler s;
            return obj != null && obj.GetType() == this.GetType() && base.Equals(obj) && (s = obj as CKISpieler) != null && s.level == this.level;
        }
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
    }
}
